
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Xml;

using Swaf.BizObj;
using Swaf.Container;
using Swaf.Security;

[assembly: WebResource("Swaf.Gui.AspNet2.BizObjListAnimi-2x.js", "application/x-javascript")]
[assembly: WebResource("Swaf.Gui.AspNet2.JqueryBizObjListAnimi.js", "application/x-javascript")]

namespace Swaf.Gui.AspNet2
{
	[ParseChildren(false)]
	public class ComplexBizObjListDisplay : BaseBizObjUserControl, IBizObjListDisplay2, IWidget
    {
        static Regex s_key = new Regex(@"(.*)_(\d+)$");
		static Regex s_buttonKey = new Regex(@"(.*)_(\d+)_(.{32})");

        protected Privileges m_privilege;
        protected IBizObj m_bizObj=null;
        protected IBizObjCursor m_bizObjCursor=null;
		protected IBizObjList m_selectedBizObjs = null;
        protected int m_maxElements=-1;
        protected bool m_maxElIsPercent = false;
        protected int m_colElements = 1;
        protected bool m_addChkboxControlClientScript = false;
        protected int m_processCount = 0;
		protected Dictionary<string, IBizObj> m_updatedBizObjs = null;
		protected string m_dataDetailBizRule = null;
		protected string m_dataSourceBizRule = null;
		protected object m_dataSourceBizRuleParam = null;
		protected int m_initialDepth = -1;
		protected string m_animiType = null;
		protected string m_childCss = "";
		protected int m_curColCount = 0;
		protected bool m_dataIsDesc = false;
		protected bool m_shouldAddDepthMacro = false;

		protected int m_currentChildListCount = -1;
		protected int m_selectedIndex = -1;
		protected Stack<IBizObj> m_parents = new Stack<IBizObj>();

		public ComplexBizObjListDisplay()
			: base()
		{
			base.m_baseCtlType = BizProcWidgetType.list;
		}

		public ComplexBizObjListDisplay(ActiveWidget widget)
			: base(widget)
		{
			base.m_baseCtlType = BizProcWidgetType.list;
		}

		static Regex s_radioSelectId = new Regex(@"_select_(?'index'\d+)_");
		protected override void  OnLoad(EventArgs e)
		{
			base.OnLoad(e);
			if (State.State.get("m_colElements") is int)
				m_colElements = (int)State.State.get("m_colElements");
			else if (State.State.get("m_colElements") == null)
				State.State.put("m_colElements", m_colElements);

			if (State.State.get("m_maxElements") is int)
				m_maxElements = (int)State.State.get("m_maxElements");
			else if (State.State.get("m_maxElements") == null)
				State.State.put("m_maxElements", m_maxElements);

			if (State.State.get("m_maxElIsPercent") is bool)
				m_maxElIsPercent = (bool)State.State.get("m_maxElIsPercent");
			else if (State.State.get("m_maxElIsPercent") == null)
				State.State.put("m_maxElIsPercent", m_maxElIsPercent);

			string selKey = Page.Request.Form[this.UniqueID + "$RC"];
			if(Cmn.IsEmpty(selKey))
				selKey = Page.Request.Form[this.ClientID + "$RC"];
			if (!Cmn.IsEmpty(selKey))
			{
				Match m = s_radioSelectId.Match(selKey);
				if (m.Success)
					m_selectedIndex = int.Parse(m.Groups["index"].Value);
			}

		}

		public virtual bool HasElements { get { return m_bizObjCursor != null && m_bizObjCursor.numElements > 0; } }
		public virtual bool IsSelected 
		{
			get
			{
				bool isIt = false;
				try
				{
					if ((m_bizObjCursor != null && m_bizObjCursor.currentElement != null) &&
						((m_bizObjCursor.currentElement.KeyValue == null && SelectedBizObjKeyValue == null) ||
						((IComparable)m_bizObjCursor.currentElement.KeyValue).CompareTo(SelectedBizObjKeyValue) == 0))
						isIt = true;
				}
				catch(Exception){}
				return isIt;
			}
		}
		public virtual bool IsThisOrChildSelected
		{
			get
			{
				bool isIt = false;
				try
				{
					if (m_bizObjCursor != null && m_bizObjCursor.currentElement != null)
					{
						if ((m_bizObjCursor.currentElement.KeyValue == null && SelectedBizObjKeyValue == null) ||
							((IComparable)m_bizObjCursor.currentElement.KeyValue).CompareTo(SelectedBizObjKeyValue) == 0)
							isIt = true;
						else
						{
							IBizObj child = m_bizObjCursor.Source.search(m_bizObjCursor.Source.objectType.KeyFieldNames, SelectedBizObjKeyValue);
							if (child != null && ((IComparable)child.get("parentId")).CompareTo(m_bizObjCursor.currentElement.KeyValue) == 0)
								isIt = true;
						}
					}
				}
				catch (Exception) { }
				return isIt;
			}
		}

		public virtual bool IsChildListEmpty(string name)
		{
			bool isIt = true;
			IBizObjList list = CurrentBizObj.get(name) as IBizObjList;
			if (list != null && list.numElements > 0)
				isIt = false;
			return isIt;
		}

		protected bool m_drivenByBizProc = true;
		public bool DrivenByBizProc { get { return (bool)getOffline("m_drivenByBizProc"); } set{putOffline("m_drivenByBizProc", value);} }

		public string MaxElements
        {
            get 
            {
                if (m_maxElIsPercent)
                    return m_maxElements + "%";
                return m_maxElements.ToString(); 
            }
            set 
            {
                if (value.EndsWith("%"))
                {
                    m_maxElIsPercent = true;
                    m_maxElements = int.Parse(value.Substring(0, value.Length - 1));
                }
                else
                    m_maxElements = int.Parse(value); 
            }
        }

        protected int realMaxElements()
        {
            if (m_maxElIsPercent)
            {
                if (m_bizObjCursor != null)
                    return (int)Math.Ceiling((m_bizObjCursor.Source.numElements * m_maxElements) / 100M);
                else
                    return -1;
            }

            return m_maxElements;
        }

		public string AnimiType { get { return m_animiType; } set { m_animiType = value; } }
		public string ChildCssClass { get { return m_childCss; } set { m_childCss = value; } }
		public bool DataIsDesc { get { return m_dataIsDesc; } set { m_dataIsDesc = value; } }


        public int ColumnElements
        {
            get 
			{
				int numCol = (int)getOffline("m_colElements");
				if (numCol == -1)
					if (m_bizObjCursor != null)
						numCol = m_bizObjCursor.Source.numElements;
					else
						numCol = 0;
				return numCol;
			}
			set { putOffline("m_colElements", value); }
        }

		//The following group of properties are meant to be called by scripts in the ascx to help guide 
		//their behavior for which html to use for a given bizobj in the list.
		public virtual string CommonColWidth
		{
			get
			{
				int w = 100 / ColumnElements;
				return w + "%";
			}
		}

		public bool IsFirst
		{
			get
			{
				bool first = (Index == 0);
				return first;
			}
		}

        public bool IsEmpty(string fieldName)
        {
            bool isIt = true;
            if (CurrentBizObj != null)
                isIt = Cmn.IsEmpty(CurrentBizObj.get(fieldName));
            return isIt;
        }

		public bool IsLast
		{
			get
			{
				//ActiveBizProc bizproc = BPC.getActiveBizProc(State.ParentBizProcIds[0]);
				//return bizproc.BizProc.SourceBizObjs[bizproc.BizProc.SourceBizObjs.numElements - 1].KeyValue.Equals(CurrentBizObj.KeyValue);
				bool last = (m_bizObjCursor != null && m_bizObjCursor.Source.numElements != 0 && CurrentBizObj != null &&
					Index == m_bizObjCursor.Source.numElements - 1);
				return last;
			}
		}

		public bool IsFirstOrLast
		{
			get
			{
				return IsFirst || IsLast;
			}
		}

		public bool IsFirstChild
		{
			get
			{
				bool first = false;
				if(m_bizObjCursor != null && m_bizObjCursor.Source.numElements != 0 && CurrentBizObj != null)
				{
					int curDepth = (int)CurrentBizObj.get("depth");
					if (Index == 0)
						first = true;
					else if (Index > 0)
					{
						IBizObj prev = m_bizObjCursor.Source[Index];
						int prevDepth = (int)prev.get("depth");
						first = prevDepth < curDepth;
					}
				}
				return first;
			}
		}

		public bool HasChildren
		{
			get
			{
				bool has = false;
				if (m_bizObjCursor != null && m_bizObjCursor.Source.numElements != 0 && CurrentBizObj != null)
				{
					int lastIndex = m_bizObjCursor.Source.numElements - 1;
					int curDepth = (int)CurrentBizObj.get("depth");
					if (Index != lastIndex)
					{
						IBizObj next = m_bizObjCursor.Source[Index + 1];
						int nextDepth = (int)next.get("depth");
						has = nextDepth > curDepth;
					}
				}
				return has;
			}
		}

		public bool IsLastChild
		{
			get
			{
				bool last = false;
				if (m_bizObjCursor != null && m_bizObjCursor.Source.numElements != 0 && CurrentBizObj != null)
				{
					int lastIndex = m_bizObjCursor.Source.numElements - 1;
					int curDepth = (int)CurrentBizObj.get("depth");
					if (Index == lastIndex)
						last = true;
					else
					{
						IBizObj next = m_bizObjCursor.Source[Index + 1];
						int nextDepth = (int)next.get("depth");
						last = nextDepth != curDepth;
					}
				}
				return last;
			}
		}


		public string getNavLink(string urlParams)
		{
			IApplication app = Application.currentApp;
			CurrentBizObj = m_bizObjCursor.currentElement;
			string navLink = LocalMacros["navLink" + CurrentBizObj.get("depth")] as string;
			if (navLink == null || navLink == "")
				navLink = LocalMacros["navLink"] as string;
			if (navLink == null)
				navLink = "";
			return app.resMgr.resolve(navLink, LocalMacros);
		}


		public int Depth
		{
			get
			{
				bool hasDepth = m_bizObjCursor.currentElement.containsField("depth");
				int curDepth = hasDepth ? (int)m_bizObjCursor.currentElement.get("depth") : -1;

				if (m_initialDepth == -1 && curDepth != -1)
				{
					IBizObjList l = m_bizObjCursor.Source;
					//Checking first and last depth deals with asc versus desc order issues.
					if(!DataIsDesc)
						m_initialDepth = (int)l[0].get("depth");
					else
						m_initialDepth = (int)l[l.numElements - 1].get("depth");

				}
				return curDepth - m_initialDepth;
			}
		}

		public int Index
		{
			get { return m_processCount; }
		}

		public string ResetColCount
		{
			get
			{
				m_curColCount = 0;
				return "";
			}
		}




        public int getNumCols() { return m_colElements; }

        #region IBizObjListDisplay Members
        public virtual void showBizObjs(IBusinessProcess bizproc, IBizObjCursor cursor)
        {
			if (Page == null)
				return;
            m_bizObjCursor = cursor;
            clearControls();
            createControls();
			
            Page.GetPostBackEventReference(this, "");
			if (m_selectedIndex >= 0 && cursor.Source.numElements > m_selectedIndex)
				bizproc.selectBizObj(cursor.Source[m_selectedIndex].KeyValue, this);

			if (AnimiType != null && cursor != null && cursor.Source.numElements > 1)
			{
				if (AnimiType.StartsWith("Fade2x"))
				{
					string scriptUrl = Page.ClientScript.GetWebResourceUrl(typeof(ComplexBizObjListDisplay), "Swaf.Gui.AspNet2.BizObjListAnimi-2x.js");
					Page.ClientScript.RegisterClientScriptInclude("CBM.Animi1", scriptUrl);
				}
				else if (AnimiType.StartsWith("JFade"))
				{
					string scriptUrl = Page.ClientScript.GetWebResourceUrl(typeof(ComplexBizObjListDisplay), "Swaf.Gui.AspNet2.JqueryBizObjListAnimi.js");
					Page.ClientScript.RegisterClientScriptInclude("CBM.Animi2", scriptUrl);
				}
			}

			Page.ClientScript.RegisterClientScriptBlock(typeof(string), this.GetType().FullName,
            @"function checkAll(fromEl, onOff)
{
    var els = document.getElementsByTagName('input');
    for(var c = 0; c < els.length; ++c)
      if(els[c].id.length > fromEl.length && els[c].id.substring(0,fromEl.length) == fromEl)
        els[c].checked = onOff;
}", true);
        }

		static protected Regex s_fade = new Regex(@"^(?'type'Fade2x|JFade)(\((?'fadeSpeed'[^,\)]+)(\s*,\s*(?'speed'\d+))*\))?$");
		protected override void Render(HtmlTextWriter writer)
		{
			base.Render(writer);
			if (AnimiType != null && m_bizObjCursor != null && m_bizObjCursor.Source.numElements > 1)
			{
				Match m = s_fade.Match(AnimiType);
				if(m.Success)
				{
					int fs;
					string fadeSpeed = "normal";
					if (!Cmn.IsEmpty(m.Groups["fadeSpeed"].Value))
						fadeSpeed = m.Groups["fadeSpeed"].Value;

					StringBuilder buff = new StringBuilder(500);
					buff.Append("new animi('").Append(ClientID).Append("','").Append(ChildCssClass);
					buff.Append("',");
					if(int.TryParse(fadeSpeed, out fs))
						buff.Append(fadeSpeed);
					else
						buff.Append("'").Append(fadeSpeed).Append("'");
					buff.Append(",[");
					CaptureCollection cc = m.Groups["speed"].Captures;
					if (cc.Count == 0)
						buff.Append(10000);
					else
						for (int cp = 0; cp < cc.Count; cp++)
						{
							buff.Append(cc[cp].Value).Append("00");//Value is specified in tenths of a second, not ms
							if (cp != cc.Count - 1)
								buff.Append(",");
						}
					buff.Append("]).go();");

					Page.ClientScript.RegisterStartupScript(typeof(ComplexBizObjListDisplay), ID, buff.ToString(), true);
					//"new animi('" + ID + "parent','"+ChildCssClass+"'," + firstTime + ","+ secondTime + ").go();", true);
				}

			}
		}

        public virtual void addBizObj(IBizObj bizObj)
        {
            //TODO: Implement this method
        }

        public virtual void removeBizObj(IBizObj bizObj)
        {
            //TODO: Implement this method
        }

        public virtual IBizObj SelectedBizObj
        {
            get
            {
                object sid = ViewState["sid"];

                if (sid != null && m_bizObjCursor != null)
                    return findBizObjFromKey(sid);
                return null;
            }
            set 
			{
				object sid = (value == null)?null:value.KeyValue;
				ViewState["sid"] = sid;
			}
        }

		public virtual object SelectedBizObjKeyValue { get { return ViewState["sid"]; } set { ViewState["sid"] = value; } }

        public virtual void getSelectedBizObjs(IBizObjList selected)
        {
        }

		public virtual void setSelectedBizObjs(IBizObjList selected)
		{
			m_selectedBizObjs = selected;
		}

        public virtual void removeSelectedBizObjs(IBizObjList selectedList)
        {
        }

		public virtual void updateBizObjs()
		{
			updateBizObjList();
		}

		public virtual void refresh()
		{
		}

        #endregion

		public void updateBizObjList()
		{
			m_updatedBizObjs = new Dictionary<string, IBizObj>();
			foreach (Control ctl in Controls)
				updateBizObjField(ctl);

			foreach (IBizObj obj in m_updatedBizObjs.Values)
				foreach (string parentBizProcId in State.ParentBizProcIds)
				{
					ActiveBizProc proc = BPC.getActiveBizProc(parentBizProcId);
					proc.BizProc.updateBizObj(obj);
				}
		}

		protected override void updateBizObj(IBizObj bizObj, string fieldName, object newVal)
		{
			base.updateBizObj(bizObj, fieldName, newVal);
			if (m_updatedBizObjs != null)
				m_updatedBizObjs[bizObj.KeyValue.ToString()] = bizObj;

		}

        public string getCtlPostfix(string postFix)
        {
            m_addChkboxControlClientScript = true;
            return ClientID + postFix;
        }

        public void processList(HtmlBlockInfo info)
        {
            if (m_bizObjCursor == null)
                return;
            m_processCount = 0;
            
            while (!m_bizObjCursor.eof)
            {
                if (!keepProcessingList(info))
                    break;

                if (m_colElements == 1 || m_colElements == -1)
                    processBizObj(info,m_bizObjCursor, true);
                else
                    info.generateBlock("");
            }
        }

        public void processInnerList(HtmlBlockInfo info)
        {
			m_curColCount = 0;
            while (!m_bizObjCursor.eof)
            {
				if (m_curColCount >= m_colElements || !keepProcessingList(info))
                    break;

				++m_curColCount;
				processBizObj(info, m_bizObjCursor, true);
            }
        }

		public void processChildList(string childName, HtmlBlockInfo info)
		{
			IBizObj parentBizObj = CurrentBizObj;
			if (parentBizObj != null)
			{
				IBizObjList childList = parentBizObj.get(childName) as IBizObjList;
				if (childList != null)
				{
					m_currentChildListCount++;
					IBizObjCursor childCur = childList.createCursor();
					while (!childCur.eof)
						processBizObj(info, childCur, false);
					CurrentBizObj = parentBizObj;
				}
			}
		}

        protected virtual void processBizObj(HtmlBlockInfo info, IBizObjCursor bizObjCursor, bool resetChildListCount)
        {
            IBizObj bizObj = bizObjCursor.currentElement;
			CurrentBizObj = bizObj;
			setupParentStack();
			int d = -1;

			if(m_shouldAddDepthMacro)
				LocalMacros["curDepth"] = d = Depth;

            string key = generateClientObjectKey(bizObj, bizObjCursor);

            IList list = info.generateBlock(key);

            foreach (string[] name in list)
                processField(name[1], name[0]);

			bizObjCursor.moveNext();
			if (resetChildListCount)
			{
				++m_processCount;
				m_currentChildListCount = -1;
			}
        }

		protected void setupParentStack()
		{
			if (CurrentBizObj != null && CurrentBizObj.containsField("depth"))
				m_parents.Push((IBizObj)CurrentBizObj.Clone());

			//if (CurrentBizObj != null && CurrentBizObj.containsField("depth"))
			//{
			//    if (m_bizObjCursor.currentPosition > 0)
			//    {
			//        IBizObj prev = m_bizObjCursor.Source[m_bizObjCursor.currentPosition - 1];
			//        int prevDepth = (int)prev.get("depth");
			//        int curDepth = (int)CurrentBizObj.get("depth");
			//        if (prevDepth < curDepth)
			//            m_parents.Push(prev);
			//        else if (curDepth < prevDepth)
			//            while (m_parents.Count > 0 && (int)m_parents.Peek().get("depth") >= curDepth)
			//                m_parents.Pop();
			//    }
			//}
		}

		protected override string lastResolve(string val)
		{
			if (m_shouldAddDepthMacro || val.Contains("$curDepth$"))
				m_shouldAddDepthMacro = true;
			return base.lastResolve(val);
		}

		protected override bool processControl(IBizObj obj, Control ctl, string fldName, ref string fldValue)
		{
			bool isit = base.processControl(obj, ctl, fldName, ref fldValue);
			if (!isit && ctl is ICheckBoxControl && fldName == ".select")
			{
				if (m_selectedBizObjs != null && m_selectedBizObjs.search(obj.type.KeyFieldNames, obj.KeyValue) != null)
					fldValue = "True";
				else
					fldValue = "False";
				isit = true;
			}
			else if (!isit && ctl is EndContainingDiv)
			{
				int curDepth = Depth;
				int nextDepth = 0;
				if (m_initialDepth != -1 && m_bizObjCursor.currentPosition < m_bizObjCursor.Source.numElements - 1)
				{
					IBizObj nextBizObj = m_bizObjCursor.Source[m_bizObjCursor.currentPosition + 1];
					nextDepth = (int)nextBizObj.get("depth") - m_initialDepth;
				}
				((EndContainingDiv)ctl).setupDepths(m_initialDepth, curDepth, nextDepth, m_parents);
			}
			return isit;
		}

        protected virtual string generateClientObjectKey(IBizObj bizObj, IBizObjCursor childCur)
        {
            Debug.Assert(bizObj != null);
			return "_" + m_bizObjCursor.currentPosition.ToString() + 
				(m_currentChildListCount != -1 ? "_" + m_currentChildListCount + "_" + childCur.currentPosition : "");
        }

        protected static Regex s_numOnly = new Regex(@"^\d+$");
        protected override string getProperBizObjFieldName(string fieldName)
        {
            fieldName = base.getProperBizObjFieldName(fieldName);
            int i = fieldName.LastIndexOf('.');
            if (i != -1 && s_numOnly.IsMatch(fieldName.Substring(i+1)))
                fieldName = fieldName.Substring(0, i);

            return fieldName;
        }

        protected virtual object getKeyFromString(IBizObjDefinition bizObjType, string key)
        {
            return bizObjType.convertValueToKeyType(key);
        }

        protected override IBizObj getBizObjFromControl(Control ctl, ref string baseCtlId)
        {
            string key2 = ctl.ID;
			Match m = s_key.Match(key2);
			if (m.Success)
			{
				baseCtlId = m.Groups[1].Value;
				string key = m.Groups[2].Value;
				int index;
				if (int.TryParse(key, out index))
				{
					string idCtlId = m_bizObjCursor.Source.objectType.KeyFieldNames + "_" + index;
					Control idCtl = FindControl(idCtlId);
					if(idCtl != null)
						return findBizObjFromKey(getControlValueForBizObjField(idCtl, m_bizObjCursor.Source.objectType.KeyFields[0] as BizObjField, null, null,true));
					return m_bizObjCursor.Source[index];
				}
			}
			else
			{
				m = s_buttonKey.Match(key2);
				if (m.Success)
				{
					baseCtlId = m.Groups[1].Value;
					string key = m.Groups[3].Value;
					return findBizObjFromKey(m_bizObjCursor.Source.objectType.convertValueToKeyType(key));
				}
			}
            return null;
        }

        protected virtual IBizObj findBizObjFromKey(object key)
        {
            foreach (IBizObj obj in m_bizObjCursor.Source)
                if (obj.KeyValue.Equals(key))
                    return obj;
            return null;
        }

        protected virtual bool keepProcessingList(HtmlBlockInfo info)
        {
            int maxEl = realMaxElements();
            return !(maxEl > -1 && m_processCount >= maxEl);
        }

		#region IBizObjListDisplay2 Members


		public event BizObjChanged BizObjUpdated;

		public event BizObjChanged BizObjDeleted;

		public event BizObjChanged BizObjAdded;

		public string DataDetailBizRule { get { return m_dataDetailBizRule; } set { m_dataDetailBizRule = value; } }
		public string DataSourceBizRule { get { return m_dataSourceBizRule; } set { m_dataSourceBizRule = value; } }
		public string DataSourceBizRuleParam { get { return m_dataSourceBizRuleParam as string; } set { m_dataSourceBizRuleParam = value; } }
		#endregion

		#region IWidget2 Members

		public void addParentProcess(IBusinessProcess parent)
		{
			LocalMacros["_controlId"] = WidgetId;
			if (parent != null)
				LocalMacros["_bizProcId"] = parent.Id;
			//base.addParentProcess(parent);
		}

		#endregion

	}
}
